From 4b0a67b55cec24f99d4842fe8ac980327beed0cb Mon Sep 17 00:00:00 2001
From: Andrew Clemons <andrew.clemons@gmail.com>
Date: Wed, 2 Aug 2017 21:22:53 +1200
Subject: [PATCH] Add support for XOAUTH2 auth

I have thus far only tested this with gmail and using node-xoauth2 for
generating the tokens.

Emailrelay still requires a client auth configuration file with four
values. The id value here can be anything since it is ignored.

I am using:

client XOAUTH2 ignored <generated token>
---
 src/gauth/gsaslclient.h          |  5 +++++
 src/gauth/gsaslclient_native.cpp | 32 +++++++++++++++++++++++++++++++-
 src/gsmtp/gclientprotocol.cpp    | 18 ++++++++++++++++--
 3 files changed, 52 insertions(+), 3 deletions(-)

diff --git a/src/gauth/gsaslclient.h b/src/gauth/gsaslclient.h
index ea12e05..d74b56d 100644
--- a/src/gauth/gsaslclient.h
+++ b/src/gauth/gsaslclient.h
@@ -67,6 +67,11 @@ class GAuth::SaslClient
 		///< Returns true if the constructor's secrets object
 		///< is valid.
 
+	std::string initial_response( const std::string & mechanism ,
+		bool & done , bool & error , bool & sensitive ) const ;
+			///< Returns an initial_response for authentication.
+			///< Returns various boolean flags by reference.
+
 	std::string response( const std::string & mechanism , const std::string & challenge , 
 		bool & done , bool & error , bool & sensitive ) const ;
 			///< Returns a response to the given challenge.
diff --git a/src/gauth/gsaslclient_native.cpp b/src/gauth/gsaslclient_native.cpp
index d0bded2..924772a 100644
--- a/src/gauth/gsaslclient_native.cpp
+++ b/src/gauth/gsaslclient_native.cpp
@@ -101,6 +101,33 @@ bool GAuth::SaslClient::active() const
 	return m_imp->m_secrets.valid() ;
 }
 
+std::string GAuth::SaslClient::initial_response( const std::string & mechanism , bool & done ,
+	bool & error , bool & sensitive ) const
+{
+	done = false ;
+	error = false ;
+	sensitive = false ;
+
+	std::string auth("AUTH") ;
+	std::string sep(" ") ;
+
+	std::string rsp ;
+	if( mechanism == "XOAUTH2" )
+	{
+		std::string secret = m_imp->m_secrets.secret(mechanism) ;
+		rsp = auth + sep + mechanism + sep + secret ;
+		error = secret.empty() ;
+		done = true ;
+		sensitive = true ;
+	}
+	else
+	{
+		rsp = auth + sep + mechanism ;
+	}
+
+	return rsp ;
+}
+
 std::string GAuth::SaslClient::response( const std::string & mechanism , const std::string & challenge , 
 	bool & done , bool & error , bool & sensitive ) const
 {
@@ -175,6 +202,7 @@ std::string GAuth::SaslClient::preferred( const G::Strings & mechanism_list ) co
 
 	const std::string login( "LOGIN" ) ;
 	const std::string plain( "PLAIN" ) ;
+	const std::string xoauth2( "XOAUTH2" ) ;
 	const std::string cram( "CRAM-MD5" ) ;
 
 	// create a them set
@@ -186,15 +214,17 @@ std::string GAuth::SaslClient::preferred( const G::Strings & mechanism_list ) co
 	std::set<std::string> us ;
 	if( !m_imp->m_secrets.id(login).empty() ) us.insert(login) ;
 	if( !m_imp->m_secrets.id(plain).empty() ) us.insert(plain) ;
+	if( !m_imp->m_secrets.id(xoauth2).empty() ) us.insert(xoauth2) ;
 	if( !m_imp->m_secrets.id(cram).empty() ) us.insert(cram) ;
 
 	// get the intersection
 	std::set<std::string> both ;
 	std::set_intersection( them.begin() , them.end() , us.begin() , us.end() , std::inserter(both,both.end()) ) ;
 
-	// preferred order: cram, plain, login
+	// preferred order: cram, xoauth2, plain, login
 	std::string m ;
 	if( m.empty() && both.find(cram) != both.end() ) m = cram ;
+	if( m.empty() && both.find(xoauth2) != both.end() ) m = xoauth2 ;
 	if( m.empty() && both.find(plain) != both.end() ) m = plain ;
 	if( m.empty() && both.find(login) != both.end() ) m = login ;
 	G_DEBUG( "GAuth::SaslClient::preferred: we prefer \"" << m << "\"" ) ;
diff --git a/src/gsmtp/gclientprotocol.cpp b/src/gsmtp/gclientprotocol.cpp
index 3ebc0c7..bbd8aca 100644
--- a/src/gsmtp/gclientprotocol.cpp
+++ b/src/gsmtp/gclientprotocol.cpp
@@ -303,8 +303,22 @@ bool GSmtp::ClientProtocol::applyEvent( const Reply & reply , bool is_start_even
 		}
 		else if( m_server_has_auth && m_sasl->active() )
 		{
-			m_state = sAuth1 ;
-			send( "AUTH " , m_auth_mechanism ) ;
+			bool done = true ;
+			bool error = false ;
+			bool sensitive = false ;
+			std::string rsp = m_sasl->initial_response( m_auth_mechanism ,
+				done , error , sensitive ) ;
+
+			if( error )
+			{
+				m_state = sAuth2 ;
+				send( "*" ) ; // ie. cancel authentication
+			}
+			else
+			{
+				m_state = done ? sAuth2 : sAuth1 ;
+				send( rsp , false , sensitive ) ;
+			}
 		}
 		else if( !m_server_has_auth && m_sasl->active() && m_must_authenticate )
 		{
